/*
 * Project: Sudoku Explainer
 * Copyright (C) 2006-2007 Nicolas Juillerat
 * 
 * Project: Sudoku Hint Engine for Android
 * Copyright (C) 2010-2013 Emanuel Moecklin
 * 
 * Available under the terms of the Lesser General Public License (LGPL)
 */
package com.onegravity.sudoku.solver.checks;

import java.text.DecimalFormat;
import java.util.LinkedHashMap;
import java.util.Map;

import android.net.Uri;

import com.onegravity.sudoku.solver.HintResource;
import com.onegravity.sudoku.solver.HintType;
import com.onegravity.sudoku.solver.core.Difficulty;
import com.onegravity.sudoku.solver.core.Rule;
import com.onegravity.sudoku.solver.core.WarningHint;
import com.onegravity.sudoku.solver.data.Grid;
import com.onegravity.sudoku.solver.util.HtmlLoader;

/**
 * A information hint produced by the Analyser class.
 * Contains an approximate rating of the sudoku, and the list of hints that
 * have been used to solve it. The actual solution is not shown, and the grid
 * is not modified by applying this hint.
 * 
 * There are three flavors of this hint:
 * - AnalysisResult: analysis successfully performed
 * - AnalysisFailed: analysis could not find a solution with the available solving techniques
 * - DifficultyBeyondNightmare: puzzle is too difficult to analyze (performance)
 *
 * @author Emanuel Moecklin
 */
public class AnalysisResult extends WarningHint {

	private static final long serialVersionUID = -3898803616843872720L;
	
    private boolean analysisFailed;
    private boolean is2Difficult;

    private double difficulty;
    private String analysisHint;

    /**
     * Constructor for all three flavors of this hint.
     * The analysisFailed flag overpowers the is2Difficult
     */
    public AnalysisResult(Grid grid, Map<Rule,Integer> rules, boolean analysisFailed, boolean is2Difficult) {

    	super(grid);
    	
    	this.analysisFailed = analysisFailed;
        this.is2Difficult = is2Difficult;
        this.difficulty = 0.0;
        
        // compute the getHintHtml() and the difficulty here so that we can dismiss the rules and ruleNames
        // this prevents crashes due to StackOverflow when serializing the class
        if (rules!=null) {
        	
        	// compute difficulty
	        for (Rule rule : rules.keySet()) {
	            if (rule.getDifficulty() > difficulty) {
	                difficulty = rule.getDifficulty();
	            }
	        }

	        Map<String, Integer> names2Count = getName2Count(rules);
        	
	        DecimalFormat format = new DecimalFormat("#0.0"); //$NON-NLS-1$
	        StringBuilder details = new StringBuilder();
	        for (String name : names2Count.keySet()) {
	            // build rule text (<#> x <rule name>) 
            	int count = names2Count.get(name);
	            details.append("<li>"); //$NON-NLS-1$
	            details.append(Integer.toString(count));
	            details.append(" x "); //$NON-NLS-1$
	            details.append(name);
	            details.append("</li>"); //$NON-NLS-1$
	        }
	        
	        Difficulty level = Difficulty.newInstance(difficulty);
	        String levelName = HintResource.getString(level.resID());
	
	        String result = HtmlLoader.loadHtml(HintResource.ANALYSIS.hint());
	        analysisHint = HtmlLoader.format(result, levelName, format.format(difficulty), details);
        }
    }
    
    /**
     * This method sums up the counts for rules with identical names
     * and generates a map name -> counts.
     * E.g. there could be two rules with the same name "Forcing Chain" with a count of 7 and 5.
     * The result would be one entry "Forcing Chain" -> 12.
     * 
     * On top of that it also adds the URL link to the name.
     * So "Forcing Chain" becomes "<a href='http://sudopedia.enjoysudoku.com/Forcing_Chain.html'>Forcing Chain</a>"
     */
    private Map<String, Integer> getName2Count(Map<Rule, Integer> rules) {
        Map<String, Integer> hints = new LinkedHashMap<String, Integer>();
        for (Rule rule : rules.keySet()) {
            int count = rules.get(rule);
            String name = rule.getName();
            Uri link = rule.getLink();
            if (link != null) {
            	name = "<a href='" + link + "'>" + name + "</a>";
            }
            if (hints.containsKey(name)) {
                hints.put(name, hints.get(name) + count);
            }
            else {
                hints.put(name, count);
            }
        }
        return hints;
    }

    @Override
    public String getHintHtml(HintType hintType) {
    	if (analysisFailed) {
            String result = HtmlLoader.loadHtml(HintResource.ANALYSIS_FAILED.hint());
            return HtmlLoader.format(result);
    	}
    	else if (is2Difficult) {
            String result = HtmlLoader.loadHtml(HintResource.ANALYSIS_PUZZLE_2_DIFFICULT.hint());
            return HtmlLoader.format(result);
    	}
    	else {
    		return analysisHint;
    	}
    }

    public double getDifficulty() {
    	return difficulty;
    }

	public int getNrOfSteps() {
		return 1;
	}

	@Override
    public String toString() {
		return analysisFailed ? HintResource.ANALYSIS_FAILED.toString() :
			   is2Difficult ? HintResource.ANALYSIS_PUZZLE_2_DIFFICULT.toString() :
			   HintResource.ANALYSIS.toString();
    }

}